﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
using System.Xml;
using System.Windows.Markup;
using System.Reflection;
using System.ComponentModel;
using System.Windows.Input;
using System.Windows.Threading;
using System.Windows.Media.Imaging;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using System.Windows.Interop;
using System.Configuration;

namespace ExtensionMethod.WPF
{
    /// <summary>
    /// Generic WPF Extension Methods
    /// </summary>
    public static class WpfExtension
    {

        #region "Extension Methods"

        #region "Visual Helper"

        /// <summary>
        /// Finds immediate parent of the child control 
        /// </summary>
        /// <typeparam name="T">Finds specific Type of parent control</typeparam>
        /// <param name="child">Child control in use</param>
        /// <returns></returns>
        public static T FindParent<T>(this DependencyObject child) where T : DependencyObject
        {
            //get parent item
            var parentObject = VisualTreeHelper.GetParent(child);

            //we've reached the end of the tree
            if (parentObject == null) return null;

            //check if the parent matches the type we're looking for
            var parent = parentObject as T;
            return parent ?? FindParent<T>(parentObject);
        }

        /// <summary>
        /// Finds child of specific type of specific name
        /// </summary>
        /// <typeparam name="T">Type of child</typeparam>
        /// <param name="parent">Current parent control</param>
        /// <param name="childName">Name of the child control to be found</param>
        /// <returns></returns>
        public static T FindChild<T>(this DependencyObject parent, string childName) where T : DependencyObject
        {
            // Confirm parent and childName are valid.  
            if (parent == null) return null;
            T foundChild = null;
            var childrenCount = VisualTreeHelper.GetChildrenCount(parent);
            for (var i = 0; i < childrenCount; i++)
            {
                var child = VisualTreeHelper.GetChild(parent, i);

                // If the child is not of the request child type child  
                var childType = child as T;
                if (childType == null)
                {
                    // recursively drill down the tree  
                    foundChild = FindChild<T>(child, childName);
                    // If the child is found, break so we do not overwrite the found child.    
                    if (foundChild != null) break;
                }
                else if (!string.IsNullOrEmpty(childName))
                {
                    var frameworkElement = child as FrameworkElement;
                    // If the child's name is set for search    
                    if (frameworkElement != null && frameworkElement.Name == childName)
                    {
                        // if the child's name is of the request name  
                        foundChild = (T)child;
                        break;
                    }
                }
                else
                {
                    // child control found.
                    foundChild = (T)child;
                    break;
                }
            }
            return foundChild;
        }

        /// <summary>
        /// Get collection of child controls of specific types
        /// </summary>
        /// <typeparam name="T">Type of controls to be fetched</typeparam>
        /// <param name="parent">Current parent</param>
        /// <returns></returns>
        public static List<T> GetVisualChildCollection<T>(this DependencyObject parent) where T : DependencyObject
        {
            List<T> visualCollection = new List<T>();
            GetVisualChildCollection(parent, visualCollection);
            return visualCollection;
        }

        #endregion

        #region UIElementCollection

        /// <summary>
        /// Brings  the control in the control collection to top
        /// </summary>
        /// <param name="collection">UI Collection</param>
        /// <param name="control">Element to be brought to front</param>
        public static void SendToFront(this UIElementCollection collection, UIElement control)
        {
            collection.Remove(control);
            collection.Add(control);
        }


        #endregion

        #region FrameworkElement

        /// <summary>
        /// Brings the control to front by setting maximum z index
        /// </summary>
        /// <param name="control">Current Element</param>
        public static void BringToFront(this FrameworkElement control)
        {
            if (control == null) return;

            Panel parent = control.Parent as Panel;
            if (parent == null) return;

            var maxZ = parent.Children.OfType<UIElement>()
              .Where(x => x != control)
              .Select(x => Panel.GetZIndex(x))
              .Max();
            Panel.SetZIndex(control, maxZ + 1);
        }

        #endregion

        #region Control

        /// <summary>
        /// Dump Control Templates of WPF Controls
        /// </summary>
        /// <param name="ctrl">Control whose xaml content of ControlTemplate has to fethced</param>
        /// <returns>XAML representation of Control Template of the control</returns>
        public static string DumpControlTemplate(this Control ctrl)
        {
            XmlWriterSettings settings = new XmlWriterSettings()
            {
                Indent = true,
                NewLineOnAttributes = true
            };

            StringBuilder strbuild = new StringBuilder();
            XmlWriter xmlwrite = XmlWriter.Create(strbuild, settings);
            XamlWriter.Save(ctrl.Template, xmlwrite);
            return strbuild.ToString();
        }

        #endregion

        #region Canvas

        /// <summary>
        /// Adds child control to canvas
        /// </summary>
        /// <typeparam name="T">Child Type to be added</typeparam>
        /// <param name="canvas">Parent canvas</param>
        /// <param name="control">Element to be added</param>
        public static void AddChild<T>(this Canvas canvas, T control)
        {
            UIElement uiElement = control as UIElement;
            if (uiElement != null && !canvas.Children.Contains(uiElement))
                canvas.Children.Add(uiElement);
        }

        /// <summary>
        /// Removes child control from canvas
        /// </summary>
        /// <typeparam name="T">Child Type to be removed</typeparam>
        /// <param name="canvas">Parent canvas</param>
        /// <param name="control">Child to be removed</param>
        public static void RemoveChild<T>(this Canvas canvas, T control)
        {
            UIElement uiElement = control as UIElement;
            if (uiElement != null && canvas.Children.Contains(uiElement))
                canvas.Children.Remove(uiElement);
        }

        /// <summary>
        /// Inserts child control from canvas
        /// </summary>
        /// <typeparam name="T">Child Type to be inserted</typeparam>
        /// <param name="canvas">Parent canvas</param>
        /// <param name="index">Index of child to be added</param>
        /// <param name="control">Child to be inserted</param>
        public static void InsertChild<T>(this Canvas canvas, int index, T control)
        {
            UIElement uiElement = control as UIElement;
            if (uiElement != null && !canvas.Children.Contains(uiElement))
                canvas.Children.Insert(index, uiElement);
        }

        /// <summary>
        /// Gets Canvas Left Position
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="control"></param>
        /// <returns></returns>
        public static double GetCanvasLeft<T>(T control)
        {
            UIElement uiElement = control as UIElement;
            if (uiElement == null)
                throw new ArgumentNullException("control");
            return (double)uiElement.GetValue(Canvas.LeftProperty);
        }

        /// <summary>
        /// Get Canvas Top
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="control"></param>
        /// <returns></returns>
        public static double GetCanvasTop<T>(T control)
        {
            UIElement uiElement = control as UIElement;
            if (uiElement == null)
                throw new ArgumentNullException("control");
            return (double)uiElement.GetValue(Canvas.TopProperty);
        }

        /// <summary>
        /// Gets Canvas position
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="control"></param>
        /// <returns></returns>
        public static System.Windows.Point GetCanvasPosition<T>(T control)
        {
            UIElement uiElement = control as UIElement;
            if (uiElement == null)
                throw new ArgumentNullException("control");

            return new System.Windows.Point(
              (double)uiElement.GetValue(Canvas.LeftProperty),
              (double)uiElement.GetValue(Canvas.TopProperty));
        }

        /// <summary>
        /// Set Canvas left position
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="control"></param>
        /// <param name="length"></param>
        public static void SetCanvasLeft<T>(T control, double length)
        {
            UIElement uiElement = control as UIElement;
            if (uiElement == null)
                throw new ArgumentNullException("control");
            uiElement.SetValue(Canvas.LeftProperty, length);
        }

        //Get Canvas Top position
        public static void SetCanvasTop<T>(T control, double length)
        {
            UIElement uiElement = control as UIElement;
            if (uiElement == null)
                throw new ArgumentNullException("control");
            uiElement.SetValue(Canvas.TopProperty, length);
        }

        //Set Canvas Position
        public static void SetCanvasPosition<T>(T control, System.Windows.Point value)
        {
            UIElement uiElement = control as UIElement;
            if (uiElement == null)
                throw new ArgumentNullException("control");
            uiElement.SetValue(Canvas.LeftProperty, value.X);
            uiElement.SetValue(Canvas.TopProperty, value.Y);
        }

        #endregion

        #region Button

        /// <summary>
        /// REmoves click event from button
        /// </summary>
        /// <param name="btn"></param>
        public static void RemoveClickEvent(this Button btn)
        {
            FieldInfo f1 = typeof(Control).GetField("EventClick", BindingFlags.Static | BindingFlags.NonPublic);
            if (f1 != null)
            {
                object obj = f1.GetValue(btn);
                PropertyInfo pi = btn.GetType().GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
                EventHandlerList list = (EventHandlerList)pi.GetValue(btn, null);
                list.RemoveHandler(obj, list[obj]);
            }
        }

        #endregion

        #region Window

        /// <summary>
        /// Set the initial focus on the given control
        /// </summary>
        /// <param name="window">Childwindow for which te focus must be set</param>
        /// <param name="focus">Control to set the focus on</param>
        public static void SetInitialFocus(this Window window, Control focus)
        {
            RoutedEventHandler fp = null; // set to null to prevent unassigned compiler error
            fp = delegate
            {
                focus.Focus();
                var tb = focus as TextBox;
                if (tb != null)
                {
                    tb.SelectAll();
                }
                // unsubscribe after first execute
                window.GotFocus -= fp;
            };

            window.GotFocus += fp;
        }

        #endregion

        #region TextBox

        public static void SetInputScope(this TextBox tb, InputScopeNameValue inputScopeNameValue)
        {
            tb.InputScope = new System.Windows.Input.InputScope()
            {
                Names = { new InputScopeName() { NameValue = inputScopeNameValue } }
            };
        }
        #endregion

        #region Dispatcher

        /// <summary>
        /// Invokes the specified <paramref name="action"/> on the given <paramref name="dispatcher"/>.
        /// </summary>
        /// <param name="dispatcher">The dispatcher on which the <paramref name="action"/> executes.</param>
        /// <param name="action">The <see cref="Action"/> to execute.</param>
        /// <param name="priority">The <see cref="DispatcherPriority"/>.  Defaults to <see cref="DispatcherPriority.ApplicationIdle"/></param>
        public static void InvokeAction(this Dispatcher dispatcher, Action action, DispatcherPriority priority)
        {
            if (dispatcher == null)
                throw new ArgumentNullException("dispatcher");
            if (action == null)
                throw new ArgumentNullException("action");
            dispatcher.Invoke(action, priority);
        }
        /// <summary>
        /// Invokes the specified <paramref name="action"/> on the given <paramref name="dispatcher"/>.
        /// </summary>
        /// <typeparam name="T">The type of the argument of the <paramref name="action"/>.</typeparam>
        /// <param name="dispatcher">The dispatcher on which the <paramref name="action"/> executes.</param>
        /// <param name="action">The <see cref="Action{T}"/> to execute.</param>
        /// <param name="arg">The first argument of the action.</param>
        /// <param name="priority">The <see cref="DispatcherPriority"/>.  Defaults to <see cref="DispatcherPriority.ApplicationIdle"/></param>
        public static void InvokeAction<T>(this Dispatcher dispatcher, Action<T> action, T arg, DispatcherPriority priority = DispatcherPriority.ApplicationIdle)
        {
            if (dispatcher == null)
                throw new ArgumentNullException("dispatcher");
            if (action == null)
                throw new ArgumentNullException("action");
            dispatcher.Invoke(action, priority, arg);
        }
        /// <summary>
        /// Invokes the specified <paramref name="action"/> on the given <paramref name="dispatcher"/>.
        /// </summary>
        /// <typeparam name="T1">The type of the first argument of the <paramref name="action"/>.</typeparam>
        /// <typeparam name="T2">The type of the second argument of the <paramref name="action"/>.</typeparam>
        /// <param name="dispatcher">The dispatcher on which the <paramref name="action"/> executes.</param>
        /// <param name="action">The <see cref="Action{T1,T2}"/> to execute.</param>
        /// <param name="arg1">The first argument of the action.</param>
        /// <param name="arg2">The second argument of the action.</param>
        /// <param name="priority">The <see cref="DispatcherPriority"/>.  Defaults to <see cref="DispatcherPriority.ApplicationIdle"/></param>
        public static void InvokeAction<T1, T2>(this Dispatcher dispatcher, Action<T1, T2> action, T1 arg1, T2 arg2, DispatcherPriority priority = DispatcherPriority.ApplicationIdle)
        {
            if (dispatcher == null)
                throw new ArgumentNullException("dispatcher");
            if (action == null)
                throw new ArgumentNullException("action");
            dispatcher.Invoke(action, priority, arg1, arg2);
        }
        /// <summary>
        /// Invokes the specified <paramref name="action"/> on the given <paramref name="dispatcher"/>.
        /// </summary>
        /// <typeparam name="T1">The type of the first argument of the <paramref name="action"/>.</typeparam>
        /// <typeparam name="T2">The type of the second argument of the <paramref name="action"/>.</typeparam>
        /// <typeparam name="T3">The type of the third argument of the <paramref name="action"/>.</typeparam>
        /// <param name="dispatcher">The dispatcher on which the <paramref name="action"/> executes.</param>
        /// <param name="action">The <see cref="Action{T1,T2,T3}"/> to execute.</param>
        /// <param name="arg1">The first argument of the action.</param>
        /// <param name="arg2">The second argument of the action.</param>
        /// <param name="arg3">The third argument of the action.</param>
        /// <param name="priority">The <see cref="DispatcherPriority"/>.  Defaults to <see cref="DispatcherPriority.ApplicationIdle"/></param>
        public static void InvokeAction<T1, T2, T3>(this Dispatcher dispatcher, Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3, DispatcherPriority priority = DispatcherPriority.ApplicationIdle)
        {
            if (dispatcher == null)
                throw new ArgumentNullException("dispatcher");
            if (action == null)
                throw new ArgumentNullException("action");
            dispatcher.Invoke(action, priority, arg1, arg2, arg3);
        }

        /// <summary>
        /// Invokes the specified <paramref name="func"/> on the given <paramref name="source"/>.
        /// </summary>
        /// <typeparam name="TResult">Output of the function</typeparam>
        /// <param name="source">Current Source</param>
        /// <param name="func">Func to be invoked </param>
        /// <returns>Returns the invoked Func output</returns>
        public static TResult InvokeAction<TResult>(this DispatcherObject source, Func<TResult> func)
        {
            if (source.Dispatcher.CheckAccess())
                return func();

            return (TResult)source.Dispatcher.Invoke(func);
        }

        /// <summary>
        /// Invokes the specified <paramref name="func"/> on the given <paramref name="source"/>.
        /// </summary>
        /// <typeparam name="TResult">Output of the function</typeparam>
        /// <param name="source">Current Source</param>
        /// <param name="func">Func to be invoked </param>
        /// <returns>Returns the invoked Func output</returns>
        public static TResult InvokeAction<T, TResult>(this T source, Func<T, TResult> func) where T : DispatcherObject
        {
            if (source.Dispatcher.CheckAccess())
                return func(source);

            return (TResult)source.Dispatcher.Invoke(func, source);
        }

        /// <summary>
        /// Invokes the specified <paramref name="func"/> on the given <paramref name="source"/>.
        /// </summary>
        /// <typeparam name="TResult">Output of the function</typeparam>
        /// <param name="source">Current Source</param>
        /// <param name="func">Func to be invoked </param>
        /// <returns>Returns the invoked Func output</returns>
        public static TResult InvokeAction<TSource, T, TResult>(this TSource source, Func<TSource, T, TResult> func, T param1) where TSource : DispatcherObject
        {
            if (source.Dispatcher.CheckAccess())
                return func(source, param1);

            return (TResult)source.Dispatcher.Invoke(func, source, param1);
        }

        /// <summary>
        /// Invokes the specified <paramref name="func"/> on the given <paramref name="source"/>.
        /// </summary>
        /// <typeparam name="TResult">Output of the function</typeparam>
        /// <param name="source">Current Source</param>
        /// <param name="func">Func to be invoked </param>
        /// <returns>Returns the invoked Func output</returns>
        public static TResult InvokeAction<TSource, T1, T2, TResult>(this TSource source, Func<TSource, T1, T2, TResult> func, T1 param1, T2 param2) where TSource : DispatcherObject
        {
            if (source.Dispatcher.CheckAccess())
                return func(source, param1, param2);

            return (TResult)source.Dispatcher.Invoke(func, source, param1, param2);
        }

        /// <summary>
        /// Invokes the specified <paramref name="func"/> on the given <paramref name="source"/>.
        /// </summary>
        /// <typeparam name="TResult">Output of the function</typeparam>
        /// <param name="source">Current Source</param>
        /// <param name="func">Func to be invoked </param>
        /// <returns>Returns the invoked Func output</returns>
        public static TResult InvokeAction<TSource, T1, T2, T3, TResult>(this TSource source, Func<TSource, T1, T2, T3, TResult> func, T1 param1, T2 param2, T3 param3) where TSource : DispatcherObject
        {
            if (source.Dispatcher.CheckAccess())
                return func(source, param1, param2, param3);

            return (TResult)source.Dispatcher.Invoke(func, source, param1, param2, param3);
        }

        /// <summary>
        /// Invokes the specified <paramref name="func"/> on the given <paramref name="source"/>.
        /// </summary>
        /// <typeparam name="TResult">Output of the function</typeparam>
        /// <param name="source">Current Source</param>
        /// <param name="func">Func to be invoked </param>
        /// <returns>Returns the invoked Func output</returns>
        public static void InvokeAction(this DispatcherObject source, Action func)
        {
            if (source.Dispatcher.CheckAccess())
                func();
            else
                source.Dispatcher.Invoke(func);
        }

        public static void InvokeAction<TSource>(this TSource source, Action<TSource> func) where TSource : DispatcherObject
        {
            if (source.Dispatcher.CheckAccess())
                func(source);
            else
                source.Dispatcher.Invoke(func, source);
        }

        public static void InvokeAction<TSource, T1>(this TSource source, Action<TSource, T1> func, T1 param1) where TSource : DispatcherObject
        {
            if (source.Dispatcher.CheckAccess())
                func(source, param1);
            else
                source.Dispatcher.Invoke(func, source, param1);
        }

        public static void InvokeAction<TSource, T1, T2>(this TSource source, Action<TSource, T1, T2> func, T1 param1, T2 param2) where TSource : DispatcherObject
        {
            if (source.Dispatcher.CheckAccess())
                func(source, param1, param2);
            else
                source.Dispatcher.Invoke(func, source, param1, param2);
        }

        public static void InvokeAction<TSource, T1, T2, T3>(this TSource source, Action<TSource, T1, T2, T3> func,
                                                         T1 param1, T2 param2, T3 param3) where TSource : DispatcherObject
        {
            if (source.Dispatcher.CheckAccess())
                func(source, param1, param2, param3);
            else
                source.Dispatcher.Invoke(func, source, param1, param2, param3);
        }

        #endregion

        #region Bitmap

        /// <summary>
        ///   Create a System.Drawing.Bitmap from the passed WPF BitmapSource instance
        /// </summary>
        /// <param name = "bitmapSource">The bitmap source.</param>
        /// <returns>The generated bitmap</returns>
        public static Bitmap ToBitmap(this BitmapSource bitmapSource)
        {
            var encoder = new BmpBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(bitmapSource));

            using (var stream = new MemoryStream())
            {
                encoder.Save(stream);
                // Nested construction required to prevent issues from closing the underlying stream
                return new Bitmap(new Bitmap(stream));
            }
        }

        public static BitmapImage ToBitmapImage(this Bitmap bitmap)
        {
            var _MemoryStream = new MemoryStream();
            bitmap.Save(_MemoryStream, ImageFormat.Png);

            var bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = _MemoryStream;
            bitmapImage.EndInit();

            return bitmapImage;
        }

        public static BitmapImage ToBitmapImage(this byte[] byteArray)
        {
            var _MemoryStream = new MemoryStream(byteArray);

            var bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = _MemoryStream;
            bitmapImage.EndInit();

            return bitmapImage;

        }

        public static BitmapSource ToBitmapSource(this byte[] byteArray)
        {
            var _MemoryStream = new System.IO.MemoryStream(byteArray);
            var _Decoder = new PngBitmapDecoder(_MemoryStream,
                BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
            var _BitmapSource = _Decoder.Frames[0];

            return _BitmapSource;
        }

        #endregion

        #region Interop

        public static bool? ShowDialog(this System.Windows.Window win, IntPtr handle)
        {
            WindowInteropHelper helper = new WindowInteropHelper(win);
            helper.Owner = handle;
            return win.ShowDialog();
        }

        #endregion

        #region UIElement

        /// <summary>
        ///   Renders the ui element into a bitmap frame.
        /// </summary>
        /// <param name = "element">The UI element.</param>
        /// <returns>The created bitmap frame</returns>
        public static BitmapSource RenderToBitmap(this UIElement element)
        {
            return element.RenderToBitmap(1);
        }

        /// <summary>
        ///   Renders the ui element into a bitmap frame using the specified scale.
        /// </summary>
        /// <param name = "element">The UI element.</param>
        /// <param name = "scale">The scale (default: 1).</param>
        /// <returns>The created bitmap frame</returns>
        public static BitmapSource RenderToBitmap(this UIElement element, double scale)
        {
            var renderWidth = (int)(element.RenderSize.Width * scale);
            var renderHeight = (int)(element.RenderSize.Height * scale);

            var renderTarget = new RenderTargetBitmap(renderWidth, renderHeight, 96, 96, PixelFormats.Pbgra32);
            var sourceBrush = new VisualBrush(element);

            var drawingVisual = new DrawingVisual();
            var drawingContext = drawingVisual.RenderOpen();

            using (drawingContext)
            {
                drawingContext.PushTransform(new ScaleTransform(scale, scale));
                drawingContext.DrawRectangle(sourceBrush, null, new Rect(new System.Windows.Point(0, 0), new System.Windows.Point(element.RenderSize.Width, element.RenderSize.Height)));
            }
            renderTarget.Render(drawingVisual);

            return renderTarget;
        }

        #endregion

        #endregion

        #region "Helper Methods"

        private static void GetVisualChildCollection<T>(DependencyObject parent, List<T> visualCollection) where T : DependencyObject
        {
            int count = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < count; i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(parent, i);
                if (child is T)
                {
                    visualCollection.Add(child as T);
                }
                else if (child != null)
                {
                    GetVisualChildCollection(child, visualCollection);
                }
            }
        }

        #endregion

    }
}
